
#include "hid.h"
#include "common.h"

void GetBuffer(uint8_t **org, uint8_t *rest, uint8_t len, uint8_t *buf) {
    uint8_t cntBit = 8;
    *buf = 0;
    while (--len) {
        *buf >>= 1;
        if ((**org) & 0x01) {
            *buf |= 0x90;
        }

        if (!--(*rest)) {
            *rest = 8;
            *org++;
        }

        if (!--cntBit) {
            cntBit = 8;
            buf++;
            *buf = 0;
        }
    }
}

void HID::destory() {
    _bInit = false;
    reportItems.clear();
}

// 获取Usage Page名称
String HID::getUsagePageName(uint32_t usagePage) {
    switch (usagePage) {
    case 0x01:
        return "Generic Desktop";
    case 0x02:
        return "Simulation Controls";
    case 0x03:
        return "VR Controls";
    case 0x04:
        return "Sport Controls";
    case 0x05:
        return "Game Controls";
    case 0x06:
        return "Generic Device Controls";
    case 0x07:
        return "Keyboard/Keypad";
    case 0x08:
        return "LEDs";
    case 0x09:
        return "Button";
    case 0x0A:
        return "Ordinal";
    case 0x0B:
        return "Telephony";
    case 0x0C:
        return "Consumer";
    default:
        return "0x" + String(usagePage, HEX);
    }
}

// 获取Usage名称
String HID::getUsageName(uint32_t usagePage, uint32_t usage) {
    if (usagePage == 0x01) { // "Generic Desktop"
        switch (usage) {
        case 0x01:
            return "Pointer";
        case 0x02:
            return "Mouse";
        case 0x30:
            return "X";
        case 0x31:
            return "Y";
        case 0x32:
            return "Z";
        case 0x33:
            return "Rx";
        case 0x34:
            return "Ry";
        case 0x35:
            return "Rz";
        case 0x38:
            return "Wheel";
        case 0x39:
            return "Hat Switch";
        default:
            return "0x" + String(usage, HEX);
        }
    } else if (currentUsagePage == 0x09) { // Button"
        return "Button " + String(usage);
    }
    return "0x" + String(usage, HEX);
}

void HID::analyzeReportDesc(uint8_t *data, size_t &offset, size_t length) {
    uint8_t prefix = data[offset++];
    uint8_t size = prefix & 0x03;
    uint8_t type = (prefix >> 2) & 0x03;
    uint8_t tag = prefix & 0xfC;

    if (size == 3)
        size = 4; // 处理特殊情况

    uint32_t value = 0;
    for (uint8_t i = 0; i < size; i++) {
        if (offset >= length)
            break;
        value |= ((uint32_t)data[offset++]) << (8 * i);
    }

    // 根据tag处理不同类型的项
    switch (tag) {
    case 0x04: // Usage Page
        currentUsagePage = value;
        if (currentUsagePage == 0x01) {
            if (currentUsage == 0x02) {
                _type = 2; // 鼠标
            } else if (currentUsage == 0x06) {
                _type = 1; // 键盘
            }
        }
        break;
    case 0x08: // Usage
        currentUsage = value;
        if (currentUsagePage == 0x01) {
            if (currentUsage == 0x02) {
                _type = 2;
            } else if (currentUsage == 0x06) {
                _type = 1;
            }
        }

        currentUsages[0]++;
        currentUsages[currentUsages[0]] = currentUsage;
        break;
    case 0x18: // Usage Minimum
        currentUsageMin = binToInt((void *)&value, size);
        currentUsages[0] = 0;
        break;
    case 0x28: // Usage Maximum
        currentUsageMax = binToInt((void *)&value, size);
        currentUsages[0] = 0;
        break;
    case 0x14: // Logical Minimum
        currentLogicalMin = binToInt((void *)&value, size);
        break;
    case 0x24: // Logical Maximum
        currentLogicalMax = binToInt((void *)&value, size);
        break;
    case 0x74: // Report Size
        currentBitSize = value;
        break;
    case 0x94: // Report Count
        currentItemCount = value;
        break;
    case 0x80: // Input
        if (!(value & 0x01)) {
            // Data
            if (value & 0x02) {
                // Variable
                for (int i = 0; i < currentItemCount; i++) {
                    HIDReportItem item;
                    item.type = tag;
                    item.reportId = currentReportId;
                    item.bitBegin = currentBitBeginInput;
                    item.bitSize = currentBitSize;
                    item.logicalMin = currentLogicalMin;
                    item.logicalMax = currentLogicalMax;
                    item.usagePage = currentUsagePage;
                    if (currentUsages[0] == 0) {
                        item.usage = currentUsageMin++;
                    } else {
                        item.usage = currentUsages[0] > 1 ? currentUsages[i + 1] : currentUsages[1];
                    }
                    reportItems.push_back(item);
                    currentBitBeginInput += currentBitSize;
                }

            } else {
                // Array
                for (int i = 0; i < currentItemCount; i++) {
                    HIDReportItem item;
                    item.type = tag;
                    item.reportId = currentReportId;
                    item.bitBegin = currentBitBeginInput;
                    item.bitSize = currentBitSize;
                    item.logicalMin = currentLogicalMin;
                    item.logicalMax = currentLogicalMax;
                    item.usagePage = currentUsagePage;
                    item.usage = 0;
                    item.usageMin = currentUsageMin;
                    item.usageMax = currentUsageMax;
                    reportItems.push_back(item);

                    currentBitBeginInput += currentBitSize;
                }
            }
        } else {
            currentBitBeginInput += currentBitSize * currentItemCount;
        }

        currentUsages[0] = 0;

        break;

    case 0x90: // Output
        if (!(value & 0x01)) {
            // Data
            if (value & 0x02) {
                // Variable
                for (int i = 0; i < currentItemCount; i++) {
                    HIDReportItem item;
                    item.type = tag;
                    item.reportId = currentReportId;
                    item.bitBegin = currentBitBeginOutput;
                    item.bitSize = currentBitSize;
                    item.logicalMin = currentLogicalMin;
                    item.logicalMax = currentLogicalMax;
                    item.usagePage = currentUsagePage;
                    if (currentUsages[0] == 0) {
                        item.usage = currentUsageMin++;
                    } else {
                        item.usage = currentUsages[0] > 1 ? currentUsages[i + 1] : currentUsages[1];
                    }
                    reportItems.push_back(item);
                    currentBitBeginOutput += currentBitSize;
                }

            } else {
                // Array
                for (int i = 0; i < currentItemCount; i++) {
                    HIDReportItem item;
                    item.type = tag;
                    item.reportId = currentReportId;
                    item.bitBegin = currentBitBeginOutput;
                    item.bitSize = currentBitSize;
                    item.logicalMin = currentLogicalMin;
                    item.logicalMax = currentLogicalMax;
                    item.usagePage = currentUsagePage;
                    item.usage = 0;
                    item.usageMin = currentUsageMin;
                    item.usageMax = currentUsageMax;
                    reportItems.push_back(item);

                    currentBitBeginOutput += currentBitSize;
                }
            }
        } else {
            currentBitBeginOutput += currentBitSize * currentItemCount;
        }

        currentUsages[0] = 0;

        break;

    case 0xB0: // Feature
        if (!(value & 0x01)) {
            // Data
            if (value & 0x02) {
                // Variable
                for (int i = 0; i < currentItemCount; i++) {
                    HIDReportItem item;
                    item.type = tag;
                    item.reportId = currentReportId;
                    item.bitBegin = currentBitBeginFeature;
                    item.bitSize = currentBitSize;
                    item.logicalMin = currentLogicalMin;
                    item.logicalMax = currentLogicalMax;
                    item.usagePage = currentUsagePage;
                    if (currentUsages[0] == 0) {
                        item.usage = currentUsageMin++;
                    } else {
                        item.usage = currentUsages[0] > 1 ? currentUsages[i + 1] : currentUsages[1];
                    }
                    reportItems.push_back(item);
                    currentBitBeginFeature += currentBitSize;
                }

            } else {
                // Array
                for (int i = 0; i < currentItemCount; i++) {
                    HIDReportItem item;
                    item.type = tag;
                    item.reportId = currentReportId;
                    item.bitBegin = currentBitBeginFeature;
                    item.bitSize = currentBitSize;
                    item.logicalMin = currentLogicalMin;
                    item.logicalMax = currentLogicalMax;
                    item.usagePage = currentUsagePage;
                    item.usage = 0;
                    item.usageMin = currentUsageMin;
                    item.usageMax = currentUsageMax;
                    reportItems.push_back(item);

                    currentBitBeginFeature += currentBitSize;
                }
            }
        } else {
            currentBitBeginFeature += currentBitSize * currentItemCount;
        }

        currentUsages[0] = 0;

        break;

    case 0x84: // Report ID
        currentReportId = value;
        break;
    }
}

// 打印解析的Report Map信息
void HID::printReportMapInfo() {
    Serial.println("==== HID Report Map Info ====");
    for (const auto &item : reportItems) {
        Serial.print("Type: ");
        switch (item.type) {
        case 0x80:
            Serial.print("Input");
            break;
        case 0x90:
            Serial.print("Output");
            break;
        case 0xB0:
            Serial.print("Feature");
            break;
        default:
            Serial.print("Unknown");
            break;
        }

        Serial.print(", Report ID: ");
        Serial.print(item.reportId);
        Serial.print(", Bit begin: ");
        Serial.print(item.bitBegin);
        Serial.print(", Bit Size: ");
        Serial.print(item.bitSize);
        Serial.print(", Logical: [");
        Serial.print(item.logicalMin);
        Serial.print(", ");
        Serial.print(item.logicalMax);
        Serial.print("]");
        Serial.print(", Usage Page: ");
        Serial.print(getUsagePageName(item.usagePage));
        Serial.print(", Usage: ");
        Serial.println(getUsageName(item.usagePage, item.usage));
    }
    Serial.printf("Total: %d bytes.\n", _ReportSize);
    Serial.printf("Type: %d.\n", _type);

    Serial.println("============================");
}

/*******************************************************************************
 * Function Name  : init
 * Description    : 根据HID报表描述符初始化
 * Input          : DataBuf  :
 *                  nLen
 * Output         : None
 * Return         : 0:正常
 *******************************************************************************/
void HID::init(uint8_t *pReportMap, size_t length) {

    _type = 0;
    currentUsagePage = 0;
    currentUsage = 0;
    currentUsages[0] = 0;
    currentLogicalMin = 0;
    currentLogicalMax = 0;
    currentBitSize = 0;
    currentItemCount = 0;
    currentReportId = 0;
    currentBitBeginInput = 0;
    currentBitBeginOutput = 0;
    currentBitBeginFeature = 0;
    reportItems.clear();
    size_t offset = 0;

    while (offset < length) {
        analyzeReportDesc(pReportMap, offset, length);
    }
    _ReportSize = currentBitBeginInput >> 3;
    printReportMapInfo();

    _bInit = true;
}
